package test.org.offsync.nio

import java.nio.channels.CancelledKeyException
import java.nio.channels.ClosedChannelException
import java.nio.channels.SelectionKey
import java.nio.channels.{SelectionKey => JavaSelectionKey}
import org.junit.runner.RunWith
import org.offsync.nio.DeadlineTimer
import org.offsync.nio.IoService
import org.offsync.nio.ServerSocketChannel
import org.offsync.nio.SocketChannel
import org.offsync.rich._
import org.offsync.util.DisposedException
import org.offsync.util.Duration
import org.offsync.util.Slot
import org.offsync.util.Time
import org.offsync.util.Trace.trace
import org.offsync.util.Window
import scala.collection.mutable.ListBuffer
import test.util.AssertionsCollector
import test.util.Timeline
import test.util.Timeline._

@RunWith(classOf[org.specs2.runner.JUnitRunner])
class SpecSocketChannel extends org.specs2.mutable.SpecificationWithJUnit {
  override def intToRichLong(v: Int) = super.intToRichLong(v)
  override def longToRichLong(v: Long) = super.longToRichLong(v)
  "Selector" should {
  
    "allow execution in selector thread" in {
      val ioService = new IoService()
      var result0: Option[Throwable] = None
      ServerSocketChannel.open("localhost", 1234).using { serverSocket =>
        serverSocket.onCanAccept(ioService) { result =>
          result0 = result
        }
        Thread.sleep(100)
      }
      
      Thread.sleep(400)
      ioService.dispose()
      result0.get.getClass().equals(classOf[CancelledKeyException]) must_== true
    }
    
    "test 2" in {
      val ioService = new IoService()
      var result0 = false
      ServerSocketChannel.open("localhost", 1234).using { listenChannel =>
        SocketChannel.open("localhost", 1234).using { clientChannel =>
          listenChannel.onCanAccept(ioService) { result =>
            if (result == None) {
              val serverChannel = listenChannel.doAccept()
              result0 = true
            }
          }
          Thread.sleep(200)
        }
      }
      Thread.sleep(400)
      ioService.dispose()
      result0 must_== true
    }
    
    "test read/write" in {
      val ioService = new IoService()
      val deadlineTimer = new DeadlineTimer()
      var result0 = false
      var complete = new Slot[Boolean](None)
      val timeline = new Timeline
      var requireThat = new AssertionsCollector
      
      ServerSocketChannel.open("localhost", 1234).using { serverChannel =>
        serverChannel.accept(ioService) { getSocket =>
          try {
            val sessionChannel = getSocket()
            val window = Window to (new Array[Byte](100))
            def performSessionRead(): Unit = {
              sessionChannel.read(ioService, window) { getBytesRead =>
                try {
                  val bytesRead = getBytesRead()
                  if (bytesRead < 0) {
                  } else {
                    val dataWindow = window.until(bytesRead)
                    val dataString = dataWindow.asString
                    sessionChannel.write(ioService, window.until(bytesRead)) { getBytesWritten =>
                      try {
                        val bytesWritten = getBytesWritten()
                        requireThat { bytesWritten must (equalTo(11) or equalTo(4)) }
                        performSessionRead()
                      } catch {
                        case e => failure("unexpected exception: " + e)
                      }
                    }
                  }
                } catch {
                  case e => failure("unexpected exception: " + e)
                }
              }
            }
            performSessionRead()
          } catch {
            case e => failure("unexpected exception: " + e)
          }
        }
        SocketChannel.open("localhost", 1234).using { clientChannel =>
          val window = Window to (new Array[Byte](100))
          def performRead(): Unit = clientChannel.read(ioService, window) { getBytesRead =>
            try {
              val bytesRead = getBytesRead()
              val readString = new String(window.until(bytesRead).asString())
              requireThat { readString must (equalTo("Hello world") or equalTo("Bye!") or equalTo("")) }
              performRead()
            } catch {
              case e: ClosedChannelException => //println("--> client channel closed")
              case e => println("unexpected exception: " + e); failure("unexpected exception: " + e)
            }
          }
          performRead()
          clientChannel.write(ioService, "Hello world".window) { getBytesWritten =>
            try {
              val bytesWritten1 = getBytesWritten()
              requireThat { bytesWritten1 must equalTo(11) }
              deadlineTimer.execute(1.seconds from Time.now) {
                println("over 1 second")
                clientChannel.write(ioService, "Bye!".window) { getBytesWritten2 =>
                  try {
                    val bytesWritten2 = getBytesWritten2()
                    println("writing: Bye!")
                    requireThat { bytesWritten2 must equalTo(4) }
                    complete.set(true)
                  } catch {
                    case e => requireThat { failure("unexpected exception: " + e) }
                  }
                }
              }
            } catch {
              case e => failure("unexpected exception: " + e)
            }
          }
          complete.get
        }
      }
      
      ioService.dispose()
      requireThat.replay()
    }
    
    "test dispose" in {
      val ioService = new IoService()
      ioService.dispose()
      ServerSocketChannel.open("localhost", 1234).using { listenChannel =>
        listenChannel.onCanAccept(ioService) { result => } must throwA[DisposedException]
      }
    }
  }
}
